class CSV {
	public static DefaultOptions = {
					delim: ',',
					quote: '"',
					rowdelim: '\n',
					comment: '#'
				}

	public constructor() {}

	public static parseOne(str, options):any {
		var parser = new CSVParser(str, options);
		if (parser.hasNext()) {
			return parser.nextRow();
		}
		return null;
	}

	public static parse(str, options):any {
		var parser = new CSVParser(str, options);
		var all = [];
		while (parser.hasNext()) {
			var ar = parser.nextRow();
			all.push(ar);
		}
		return all;
	}

	public static bindColumns(rows, colnames):any {
		if (!Array.prototype.map) {
			Array.prototype.map = function(callback, thisArg) {
				var T, A, k;
				if (this === null) {
					throw new TypeError(" this is null or not defined");
				}
				var O = Object(this);
				var len = O.length >>> 0;
				if ({}.toString.call(callback) != "[object Function]") {
					throw new TypeError(callback + " is not a function");
				}
				if (thisArg) {
					T = thisArg;
				}
				A = new Array(len);
				k = 0;
				while(k < len) {
					var kValue, mappedValue;
					if (k in O) {
						kValue = O[ k ];
						mappedValue = callback.call(T, kValue, k, O);
						A[ k ] = mappedValue;
					}
					k++;
				}
				return A;
			};
		}

		//找到以#开头的行的index
		var indexs = [];
		var length = rows.length;
		for (var j = 0; j < length; j++) {
			if (rows[j][0].indexOf(CSV.DefaultOptions.comment) === 0) {
				indexs.push(j);
			}
		}
		//从rows中移除以#开头的行
		length = indexs.length;
		for (var k = length-1; k >= 0; k--) {
			var index = indexs[k];
			rows.splice(index, 1);
		}

		if (!colnames) {
			colnames = rows.shift();
		}

		var rowObj = {};
		var rowList = rows.map(function(row) {
						var obj = {};
						for (var i = 0; i < row.length; i++) {
							obj[colnames[i]] = row[i];
						}

						rowObj[row[0]] = obj;
						return obj;
					});

		return [rowObj, rowList]; 
	}
}

class CSVParser {
	private str:any;
	private options:any;
	private pos:any;
	private endpos:any;
	private lineNo:any;
	public constructor(str, options) {
		this.str = str;
		this.options = CSV.DefaultOptions;
		if (options) {
			options.delim = options.delim || CSV.DefaultOptions.delim;
			options.quote = options.quote || CSV.DefaultOptions.quote;
			if (options.quote.length != 1) {
				throw new RangeError('options.quote should be only 1 char');
			}
			options.rowdelim = options.rowdelim || CSV.DefaultOptions.rowdelim;
			this.options = options;
		}

		this.pos = 0;
		this.endpos = str.length;

		this.lineNo = 1;
	}

	public next(s):any {
		if (this.pos < this.endpos) {
			var len = s.length;
			if (this.str.substring(this.pos, this.pos + len) == s) {
				this.pos += len;
				return true;
			}
		}
		return false;
	}

	public ahead(s:any = null):any {
		if (this.pos < this.endpos) {
			if (!s) {
				return true;
			}
			var len = s.length;
			if (this.str.substring(this.pos, this.pos + len) == s) {
				return true;
			}
		}
		return false;
	}

	public quotedField():any {
		function countMatches(str, patt) {
			var count = 0;
			var i = str.indexOf(patt);
			while (i > 0) {
				count++;
				i = str.indexOf(patt, i + patt.length);
			}
			return count;
		}

		var mark = this.pos;
		if (!this.next(this.options.quote)) { this.pos = mark; return null; }
		var tmp = [];
		var start = this.pos;
		while (start < this.endpos) {
			var end = this.str.indexOf(this.options.quote, start);
			if (end < 0) {
				console.log('Error: line ' + this.lineNo + ': missing close quote');
			}
			var part = this.str.substring(start, end);
			this.lineNo += countMatches(part, '\n');
			tmp.push(part);
			if ((end + 1 < this.endpos) && (this.str.charAt(end + 1) == this.options.quote)) {
				start = end + 2;
				end = this.str.indexOf(this.options.quote, start);
			} else {
				this.pos = end + 1;
				break;
			}
		}
		return tmp.join(this.options.quote);
	}

	public normalField():any {
		var begin = this.pos;
		var idelim = this.str.indexOf(this.options.delim, begin);
		if (idelim < 0) {
			idelim = this.endpos;
		}
		var irowdelim = this.str.indexOf(this.options.rowdelim, begin);
		if (irowdelim < 0) {
			irowdelim = this.endpos;
		}
		this.pos = Math.min(idelim, irowdelim);
		return this.str.substring(begin, this.pos);
	}

	/**
	 * nextField: quotedField + normalField
	 */
	public nextField():any {
		var tmp = this.quotedField();
		if (tmp !== null) return tmp;
		return this.normalField();
	}

	/**
	 * nextRow_0: ',' * nextField
	 */
	public nextRow_0():any {
		var mark = this.pos;
		if (!this.next(this.options.delim)) { this.pos = mark; return null; }
		var tmp = this.nextField();
		if (tmp === null) { this.pos = mark; return null; }
		return tmp;
	}

	public nextRow():any {
		var ar = [];
		var mark = this.pos;
		var tmp = this.nextField();
		if (tmp === null) { this.pos = mark; return null; }
		ar.push(tmp);
		tmp = this.nextRow_0();
		while (tmp !== null) {
			ar.push(tmp);
			tmp = this.nextRow_0();
		}
		if (!(this.next(this.options.rowdelim) || !this.ahead())) {
			console.log('Error: line ' + this.lineNo + ': ' + this.str.substring(Math.max(this.pos - 5, 0), this.pos + 5));
			this.pos = mark; return null;
		}
		if (this.str.charAt(this.pos - 1) == '\n') {
			this.lineNo++;
		}
		return ar;
	}

	public hasNext():any {
		return this.ahead();
	}
}